/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cmdSystem.cpp - script command processing
//

// TODO:
// - alias commands


#include "common.h"


#define CMDS_HASH_SIZE			512

#define MAX_LIST_CMDS			2048

struct cmd_t {
	const char *		name;
	cmdFunction_t		function;
	const char *		description;
	argCompletion_t		argCompletion;

	cmd_t *				next;
	cmd_t *				nextHash;
};

static cmd_t *			cmd_functionsHashTable[CMDS_HASH_SIZE];
static cmd_t *			cmd_functions;

static int				cmd_wait;


/*
 ==================
 Cmd_AddCommand
 ==================
*/
void Cmd_AddCommand (const char *name, cmdFunction_t function, const char *description, argCompletion_t argCompletion){

	cmd_t	*cmd;
	uint	hashKey;

	// Fail if the command already exists
	hashKey = Str_HashKey(name, CMDS_HASH_SIZE, false);

	for (cmd = cmd_functionsHashTable[hashKey]; cmd; cmd = cmd->nextHash){
		if (!Str_ICompare(cmd->name, name)){
			if (function != NULL)
				Com_Printf(S_COLOR_YELLOW "Cmd_AddCommand: \"%s\" already defined\n", name);

			return;
		}
	}

	// Allocate a new command
	cmd = (cmd_t *)Mem_Alloc(sizeof(cmd_t), TAG_COMMON);

	cmd->name = Mem_DupString(name, TAG_COMMON);
	cmd->function = function;
	cmd->description = (description) ? Mem_DupString(description, TAG_COMMON) : NULL;
	cmd->argCompletion = argCompletion;

	// Link the command in
	cmd->next = cmd_functions;
	cmd_functions = cmd;

	// Add to hash table
	cmd->nextHash = cmd_functionsHashTable[hashKey];
	cmd_functionsHashTable[hashKey] = cmd;
}

/*
 ==================
 Cmd_RemoveCommand
 ==================
*/
void Cmd_RemoveCommand (const char *name){

	cmd_t	*cmd, **prevCmd;
	cmd_t	*hash, **prevHash;
	uint	hashKey;

	// Find the command
	prevCmd = &cmd_functions;

	while (1){
		cmd = *prevCmd;
		if (!cmd)
			break;		// Not found

		if (!Str_ICompare(cmd->name, name)){
			// Remove from hash table
			hashKey = Str_HashKey(cmd->name, CMDS_HASH_SIZE, false);

			prevHash = &cmd_functionsHashTable[hashKey];

			while (1){
				hash = *prevHash;
				if (!hash)
					break;		// Not found

				if (!Str_ICompare(hash->name, cmd->name)){
					*prevHash = hash->nextHash;
					break;
				}

				prevHash = &hash->nextHash;
			}

			// Remove the command
			*prevCmd = cmd->next;

			if (cmd->name)
				Mem_Free(cmd->name);
			if (cmd->description)
				Mem_Free(cmd->description);

			Mem_Free(cmd);

			return;
		}

		prevCmd = &cmd->next;
	}
}

/*
 ==================
 Cmd_CommandCompletion
 ==================
*/
void Cmd_CommandCompletion (void (*callback)(const char *string)){

	cmd_t	*cmd;

	for (cmd = cmd_functions; cmd; cmd = cmd->next)
		callback(cmd->name);
}

/*
 ==================
 Cmd_ArgumentCompletion
 ==================
*/
void Cmd_ArgumentCompletion (void (*callback)(const char *string)){

	cmd_t		*cmd;
	const char	*name;
	uint		hashKey;

	name = Cmd_Argv(0);

	hashKey = Str_HashKey(name, CMDS_HASH_SIZE, false);

	for (cmd = cmd_functionsHashTable[hashKey]; cmd; cmd = cmd->nextHash){
		if (!cmd->argCompletion)
			continue;

		if (!Str_ICompare(cmd->name, name)){
			cmd->argCompletion(callback);
			return;
		}
	}
}

/*
 ==================
 Cmd_ArgCompletion_PathExtension
 ==================
*/
void Cmd_ArgCompletion_PathExtension (void (*callback)(const char *string), const char *path, const char *extension, bool stripPath){

	char		string[MAX_PATH_LENGTH];
	char		argPath[MAX_PATH_LENGTH];
	bool		cmdOnly;
	const char	**fileList;
	int			numFiles;
	int			i, skip;

	if (Cmd_Argc() == 1)
		cmdOnly = true;
	else {
		cmdOnly = false;

		if (stripPath)
			Str_SPrintf(string, sizeof(string), "%s/%s", path, Cmd_Argv(1));
		else
			Str_SPrintf(string, sizeof(string), "/%s", Cmd_Argv(1));

		Str_StripFileName(string);

		if (string[0] != '/')
			Str_Copy(argPath, string, sizeof(argPath));
		else
			Str_Copy(argPath, string + 1, sizeof(argPath));
	}

	if (!stripPath || !path[0])
		skip = 0;
	else
		skip = Str_Length(path) + 1;

	// List directories
	if (cmdOnly){
		fileList = FS_ListFiles(path, "/", false, &numFiles);

		if (path[0]){
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/%s/", path, fileList[i]);

				callback(string + skip);
			}
		}
		else {
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/", fileList[i]);

				callback(string + skip);
			}
		}

		FS_FreeFileList(fileList);
	}
	else {
		fileList = FS_ListFiles(argPath, "/", false, &numFiles);

		if (argPath[0]){
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/%s/", argPath, fileList[i]);
				
				callback(string + skip);
			}
		}
		else {
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/", fileList[i]);

				callback(string + skip);
			}
		}

		FS_FreeFileList(fileList);
	}

	// List files
	if (cmdOnly){
		fileList = FS_ListFiles(path, extension, false, &numFiles);

		if (path[0]){
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/%s", path, fileList[i]);

				callback(string + skip);
			}
		}
		else {
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s", fileList[i]);

				callback(string + skip);
			}
		}

		FS_FreeFileList(fileList);
	}
	else {
		fileList = FS_ListFiles(argPath, extension, false, &numFiles);

		if (argPath[0]){
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s/%s", argPath, fileList[i]);

				callback(string + skip);
			}
		}
		else {
			for (i = 0; i < numFiles; i++){
				Str_SPrintf(string, sizeof(string), "%s", fileList[i]);

				callback(string + skip);
			}
		}

		FS_FreeFileList(fileList);
	}
}

/*
 ==================
 Cmd_ArgCompletion_Definition
 ==================
*/
void Cmd_ArgCompletion_Definition (void (*callback)(const char *string), int type){

	Def_EnumDefs((defType_t)type, callback);
}

/*
 ==================
 Cmd_ArgCompletion_FileName
 ==================
*/
void Cmd_ArgCompletion_FileName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "", NULL, false);
}

/*
 ==================
 Cmd_ArgCompletion_ConfigName
 ==================
*/
void Cmd_ArgCompletion_ConfigName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "", ".cfg", false);
}

/*
 ==================
 Cmd_ArgCompletion_DemoName
 ==================
*/
void Cmd_ArgCompletion_DemoName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "demos", ".demo", true);
}

/*
 ==================
 Cmd_ArgCompletion_MapName
 ==================
*/
void Cmd_ArgCompletion_MapName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "maps", ".bsp", true);
}

/*
 ==================
 Cmd_ArgCompletion_ModelName
 ==================
*/
void Cmd_ArgCompletion_ModelName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "progs", ".mdl", false);
	Cmd_ArgCompletion_PathExtension(callback, "models", ".md3", false);
}

/*
 ==================
 Cmd_ArgCompletion_SpriteName
 ==================
*/
void Cmd_ArgCompletion_SpriteName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "models", ".spr", false);
}

/*
 ==================
 Cmd_ArgCompletion_ImageName
 ==================
*/
void Cmd_ArgCompletion_ImageName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "colors", ".tga", false);

	Cmd_ArgCompletion_PathExtension(callback, "env", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "env", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "gfx", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "gfx", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "guis", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "guis", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "imgprog", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "imgprog", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "lights", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "lights", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "models", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "models", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "textures", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "textures", ".dds", false);

	Cmd_ArgCompletion_PathExtension(callback, "videos", ".tga", false);
	Cmd_ArgCompletion_PathExtension(callback, "videos", ".dds", false);
}

/*
 ==================
 Cmd_ArgCompletion_SoundName
 ==================
*/
void Cmd_ArgCompletion_SoundName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "sounds", ".wav", false);
}

/*
 ==================
 Cmd_ArgCompletion_MusicName
 ==================
*/
void Cmd_ArgCompletion_MusicName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "music", ".ogg", false);
}

/*
 ==================
 Cmd_ArgCompletion_VideoName
 ==================
*/
void Cmd_ArgCompletion_VideoName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "videos", ".RoQ", false);
}

/*
 ==================
 Cmd_ArgCompletion_SkinName
 ==================
*/
void Cmd_ArgCompletion_SkinName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_Definition(callback, DEF_SKIN);
}

/*
 ==================
 Cmd_ArgCompletion_MaterialName
 ==================
*/
void Cmd_ArgCompletion_MaterialName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_Definition(callback, DEF_MATERIAL);
}

/*
 ==================
 Cmd_ArgCompletion_SoundShaderName
 ==================
*/
void Cmd_ArgCompletion_SoundShaderName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_Definition(callback, DEF_SOUNDSHADER);
}

/*
 ==================
 Cmd_ArgCompletion_ParticleName
 ==================
*/
void Cmd_ArgCompletion_ParticleName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_Definition(callback, DEF_PARTICLE);
}

/*
 ==================
 Cmd_ArgCompletion_FXName
 ==================
*/
void Cmd_ArgCompletion_FXName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_Definition(callback, DEF_FX);
}

/*
 ==================
 Cmd_ArgCompletion_GUIName
 ==================
*/
void Cmd_ArgCompletion_GUIName (void (*callback)(const char *string)){

	Cmd_ArgCompletion_PathExtension(callback, "guis", ".gui", true);
}


/*
 ==============================================================================

 COMMAND ARGUMENTS

 ==============================================================================
*/

#define MAX_COMMAND_ARGS		64
#define MAX_COMMAND_STRING		MAX_STRING_LENGTH * 2

static int			cmd_argc;
static char *		cmd_argv[MAX_COMMAND_ARGS];
static char			cmd_string[MAX_COMMAND_STRING];
static char			cmd_tokenized[MAX_COMMAND_STRING];


/*
 ==================
 Cmd_Argc
 ==================
*/
int	Cmd_Argc (){

	return cmd_argc;
}

/*
 ==================
 Cmd_Argv
 ==================
*/
const char *Cmd_Argv (int arg){

	if (arg < 0 || arg >= cmd_argc)
		return "";

	return cmd_argv[arg];	
}

/*
 ==================
 Cmd_Args
 ==================
*/
const char *Cmd_Args (int start, int end, bool tokenized){

	static char	args[MAX_COMMAND_STRING];
	const char	*string;
	bool		inQuote;
	int			i, len;

	if (!cmd_argc)
		return "";

	if (start < 0)
		start = 0;
	else if (start > cmd_argc)
		start = cmd_argc;

	if (end < 0)
		end = cmd_argc - 1;
	else if (end >= cmd_argc)
		end = cmd_argc - 1;

	if (tokenized){
		args[0] = 0;

		for (i = start; i <= end; i++){
			Str_Append(args, cmd_argv[i], sizeof(args));

			if (i != end)
				Str_Append(args, " ", sizeof(args));
		}
	}
	else {
		string = cmd_string;

		for (i = 0; i < start; i++){
			while (*string){
				if (*string != ' ')
					break;

				string++;
			}

			inQuote = false;

			while (*string){
				if (*string == '"')
					inQuote = !inQuote;

				if (*string == ' ' && !inQuote){
					string++;
					break;
				}

				string++;
			}
		}

		len = 0;

		for (i = start; i <= end; i++){
			while (*string){
				if (*string != ' ')
					break;

				args[len++] = *string++;
			}

			inQuote = false;

			while (*string){
				if (*string == '"')
					inQuote = !inQuote;

				if (*string == ' ' && !inQuote)
					break;

				args[len++] = *string++;
			}
		}

		args[len] = 0;
	}

	return args;
}

/*
 ==================
 Cmd_TokenizeString
 ==================
*/
void Cmd_TokenizeString (const char *text){

	script_t	*script;
	token_t		token;
	cvar_t		*cvar;
	const char	*string;
	int			len = 0;

	// Clear the previous args
	cmd_argc = 0;

	if (!text || !text[0])
		return;

	// Save a copy of the original command string
	Str_Copy(cmd_string, text, sizeof(cmd_string));

	// Parse it
	script = PS_LoadScriptMemory("TokenizeString", text, Str_Length(text), 1);
	if (!script)
		return;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_PARSEPRIMITIVES);

	while (1){
		if (cmd_argc == MAX_COMMAND_ARGS || len == MAX_COMMAND_STRING)
			break;		// This is usually something malicious

		// Read a token
		if (!PS_ReadToken(script, &token))
			break;

		string = token.string;

		// Handle $CVar expansion
		if (token.type == TT_PRIMITIVE && token.string[0] == '$'){
			cvar = CVar_FindVariable(&token.string[1]);
			if (cvar)
				string = cvar->value;
			else
				string = "<UNKNOWN CVAR>";
		}

		// Add the token
		cmd_argv[cmd_argc++] = &cmd_tokenized[len];

		while (*string){
			if (len == MAX_COMMAND_STRING - 1)
				break;		// This is usually something malicious

			cmd_tokenized[len++] = *string++;
		}

		cmd_tokenized[len++] = 0;
	}

	PS_FreeScript(script);
}


/*
 ==============================================================================

 COMMAND EXECUTION

 ==============================================================================
*/


/*
 ==================
 Cmd_ExecuteString
 ==================
*/
static void Cmd_ExecuteString (const char *text){

	cmd_t		*cmd;
	const char	*name;
	uint		hashKey;

	// Execute the command line
	Cmd_TokenizeString(text);

	if (!Cmd_Argc())
		return;		// No tokens

	name = Cmd_Argv(0);

	// Check commands
	hashKey = Str_HashKey(name, CMDS_HASH_SIZE, false);

	for (cmd = cmd_functionsHashTable[hashKey]; cmd; cmd = cmd->nextHash){
		if (!Str_ICompare(cmd->name, name)){
			if (cmd->function){
				cmd->function();
				return;
			}

			// Send it as a server command if we are connected
			CL_ForwardCommandToServer();
			return;
		}
	}

	// Check variables
	if (CVar_Command())
		return;

	Com_Printf("Unknown command \"%s\"\n", name);
}


/*
 ==============================================================================

 COMMAND TEXT BUFFERING

 ==============================================================================
*/

#define MAX_COMMAND_BUFFER		32768

struct cmdBuffer_t {
	char					text[MAX_COMMAND_BUFFER];
	int						size;
};

static cmdBuffer_t			cmd_buffer;


/*
 ==================
 Cmd_AppendText
 ==================
*/
void Cmd_AppendText (const char *text){

	int		len;

	len = Str_Length(text) + 1;
	if (len == 1)
		return;

	if (cmd_buffer.size + len > MAX_COMMAND_BUFFER){
		Com_Printf(S_COLOR_RED "Cmd_AppendText: overflow\n");
		return;
	}

	// Append the entire text
	Mem_Copy(cmd_buffer.text + cmd_buffer.size, text, len-1);
	cmd_buffer.size += len;

	// Add a \n
	cmd_buffer.text[cmd_buffer.size-1] = '\n';
}

/*
 ==================
 Cmd_InsertText
 ==================
*/
void Cmd_InsertText (const char *text){

	int		len;
	int		i;

	len = Str_Length(text) + 1;
	if (len == 1)
		return;

	if (cmd_buffer.size + len > MAX_COMMAND_BUFFER){
		Com_Printf(S_COLOR_RED "Cmd_InsertText: overflow\n");
		return;
	}

	// Move any commands still remaining in the buffer
	for (i = cmd_buffer.size - 1; i >= 0; i--)
		cmd_buffer.text[i+len] = cmd_buffer.text[i];

	// Insert the entire text
	Mem_Copy(cmd_buffer.text, text, len-1);
	cmd_buffer.size += len;

	// Add a \n
	cmd_buffer.text[len-1] = '\n';
}

/*
 ==================
 Cmd_ExecuteText
 ==================
*/
void Cmd_ExecuteText (cmdExec_t exec, const char *text){

	switch (exec){
	case CMD_EXEC_APPEND:
		Cmd_AppendText(text);
		break;
	case CMD_EXEC_INSERT:
		Cmd_InsertText(text);
		break;
	case CMD_EXEC_NOW:
		Cmd_ExecuteString(text);
		break;
	default:
		Com_Error(true, "Cmd_ExecuteText: bad exec");
	}
}

/*
 ==================
 Cmd_ExecuteBuffer
 ==================
*/
void Cmd_ExecuteBuffer (){

	char	line[MAX_STRING_LENGTH];
	char	*text;
	bool	inQuote;
	int		i, len;

	while (cmd_buffer.size){
		if (cmd_wait){
			// Skip out while text still remains in the buffer, leaving it for
			// next frame
			cmd_wait--;
			break;
		}

		// Find a \n or ; line break
		text = cmd_buffer.text;
		inQuote = false;

		for (len = 0; len < cmd_buffer.size; len++){
			if (text[len] == '"')
				inQuote = !inQuote;
			if (text[len] == ';' && !inQuote)
				break;
			if (text[len] == '\n')
				break;
		}

		if (len > MAX_STRING_LENGTH - 1)
			len = MAX_STRING_LENGTH - 1;

		Mem_Copy(line, text, len);
		line[len] = 0;

		// Delete the text from the command buffer and move remaining commands
		// down. This is necessary because some commands (exec, vstr, etc) can
		// insert data at the beginning of the buffer.
		if (len == cmd_buffer.size)
			cmd_buffer.size = 0;
		else {
			len++;
			cmd_buffer.size -= len;

			for (i = 0; i < cmd_buffer.size; i++)
				cmd_buffer.text[i] = cmd_buffer.text[i+len];
		}

		// Execute the command line
		Cmd_ExecuteString(line);
	}
}

/*
 ==================
 Cmd_ClearBuffer
 ==================
*/
static void Cmd_ClearBuffer (){

	cmd_buffer.size = 0;
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 Cmd_SortCmdList
 ==================
*/
static int Cmd_SortCmdList (const void *elem1, const void *elem2){

	const cmd_t	*cmd1 = *(const cmd_t **)elem1;
	const cmd_t	*cmd2 = *(const cmd_t **)elem2;

	return Str_Compare(cmd1->name, cmd2->name);
}

/*
 ==================
 Cmd_Wait_f
 ==================
*/
static void Cmd_Wait_f (){

	int		frames;

	if (Cmd_Argc() > 2){
		Com_Printf("Usage: wait [frames]\n");
		return;
	}

	if (Cmd_Argc() == 1){
		cmd_wait++;
		return;
	}

	frames = Str_ToInteger(Cmd_Argv(1));
	if (frames <= 0)
		return;

	cmd_wait += frames;
}

/*
 ==================
 Cmd_Exec_f
 ==================
*/
static void Cmd_Exec_f (){

	char	name[MAX_PATH_LENGTH];
	char	*text;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: exec <fileName>\n");
		return;
	}

	Str_Copy(name, Cmd_Argv(1), sizeof(name));
	Str_DefaultFileExtension(name, sizeof(name), ".cfg");

	// Load the file
	FS_ReadFile(name, (void **)&text);
	if (!text){
		Com_Printf("Couldn't exec %s\n", name);
		return;
	}

	Com_Printf("Execing %s\n", name);

	Cmd_InsertText(text);

	FS_FreeFile(text);
}

/*
 ==================
 Cmd_Echo_f
 ==================
*/
static void Cmd_Echo_f (){

	int		i;
	
	for (i = 1; i < Cmd_Argc(); i++)
		Com_Printf("%s ", Cmd_Argv(i));
	
	Com_Printf("\n");
}

/*
 ==================
 Cmd_Tokenize_f
 ==================
*/
static void Cmd_Tokenize_f (){

	int		i;

	for (i = 1; i < Cmd_Argc(); i++)
		Com_Printf("%3i: %s\n", i, Cmd_Argv(i));
}

/*
 ==================
 
 ==================
*/
static void Cmd_Alias_f (){

}

/*
 ==================
 Cmd_ListCmds_f
 ==================
*/
static void Cmd_ListCmds_f (){

	cmd_t		*cmd, *list[MAX_LIST_CMDS];
	const char	*filter = NULL;
	int			found = 0, total = 0;
	int			i;

	if (Cmd_Argc() > 2){
		Com_Printf("Usage: listCmds [filter]\n");
		return;
	}

	if (Cmd_Argc() == 2)
		filter = Cmd_Argv(1);

	// Find matching commands
	for (cmd = cmd_functions; cmd; cmd = cmd->next){
		total++;

		if (found == MAX_LIST_CMDS)
			continue;

		if (filter){
			if (!Str_MatchFilter(cmd->name, filter, false))
				continue;
		}

		list[found++] = cmd;
	}

	// Sort the list
	qsort(list, found, sizeof(cmd_t *), Cmd_SortCmdList);

	// Print the list
	for (i = 0; i < found; i++){
		cmd = list[i];

		Com_Printf("%-32s", cmd->name);

		if (cmd->description)
			Com_Printf(" %s\n", cmd->description);
		else
			Com_Printf("\n");
	}

	if (found == MAX_LIST_CMDS)
		Com_Printf("...\n");

	Com_Printf("--------------------\n");
	Com_Printf("%i cmds listed (%i total cmds)\n", found, total);
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/

/*
 ==================
 Cmd_Init
 ==================
*/
void Cmd_Init (){

	// Add commands
	Cmd_AddCommand("wait", Cmd_Wait_f, "Delays remaining commands a given number of frames", NULL);
	Cmd_AddCommand("exec", Cmd_Exec_f, "Executes a config file", Cmd_ArgCompletion_ConfigName);
	Cmd_AddCommand("echo", Cmd_Echo_f, "Echoes text to the console", NULL);
	Cmd_AddCommand("tokenize", Cmd_Tokenize_f, "Tokenizes a string", NULL);
	Cmd_AddCommand("alias", Cmd_Alias_f, "Creates an alias command", NULL);
	Cmd_AddCommand("listCmds", Cmd_ListCmds_f, "Lists commands", NULL);

	// Clear wait counter
	cmd_wait = 0;
	
	// Clear command buffer
	Cmd_ClearBuffer();
}

/*
 ==================
 Cmd_Shutdown
 ==================
*/
void Cmd_Shutdown (){

	// Remove commands
	Cmd_RemoveCommand("wait");
	Cmd_RemoveCommand("exec");
	Cmd_RemoveCommand("echo");
	Cmd_RemoveCommand("tokenize");
	Cmd_RemoveCommand("alias");
	Cmd_RemoveCommand("listCmds");

	// Clear command list
	Mem_Fill(cmd_functionsHashTable, 0, sizeof(cmd_functionsHashTable));

	cmd_functions = NULL;
}